
import java.util.ArrayList;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.swing.JFrame;
import com.jogamp.newt.event.KeyAdapter;

import builder.Build;
import builder.Face;
import builder.FaceVertex;
import builder.Material;
import parser.Parse;

	 
	public class Controller {
		
		private static Model model;
		private static Renderer renderer;
		public static Build builder;
		
	    public static void main(String[] args) {
	    	
	    	model = new Model();
	    	renderer = new Renderer();
	    	
	    	String filename = args[0];
	        String defaultTextureMaterial = args[1];
	    	
	        // Parser things:
	        
	        System.err.println("Parsing WaveFront OBJ file");
	        builder = new Build();
	        Parse obj = null;
	        try {
	            obj = new Parse(builder, filename);
	        } catch (java.io.FileNotFoundException e) {
	            System.err.println("Exception loading object!  e=" + e);
	             e.printStackTrace();
	        } catch (java.io.IOException e) {
	            System.err.println("Exception loading object!  e=" + e);
	            e.printStackTrace();
	        }
	        System.err.println("Done parsing WaveFront OBJ file");
	        
	        System.err.println("Splitting OBJ file faces into list of faces per material");
	        ArrayList<ArrayList<Face>> facesByTextureList = createFaceListsByMaterial(builder);
	        System.err.println("Done splitting OBJ file faces into list of faces per material, ended up with " + facesByTextureList.size() + " lists of faces.");

	        System.err.println("Loading default texture =" + defaultTextureMaterial);
	        TextureLoader textureLoader = new TextureLoader();
	        int defaultTextureID = setUpDefaultTexture(textureLoader, defaultTextureMaterial);
	        System.err.println("Done loading default texture =" + defaultTextureMaterial);

	        int currentTextureID = -1;
	        for (ArrayList<Face> faceList : facesByTextureList) {
	            if (faceList.isEmpty()) {
	                System.err.println("ERROR: got an empty face list.  That shouldn't be possible.");
	                continue;
	            }
	            System.err.println("Getting material " + faceList.get(0).material);
	            currentTextureID = getMaterialID(faceList.get(0).material, defaultTextureID, builder, textureLoader);
	            System.err.println("Splitting any quads and throwing any faces with > 4 vertices.");
	            ArrayList<Face> triangleList = splitQuads(faceList);
	            System.err.println("Calculating any missing vertex normals.");
	            calcMissingVertexNormals(triangleList);
	            System.err.println("Ready to build VBO of " + triangleList.size() + " triangles");;

	            if (triangleList.size() <= 0) {
	                continue;
	            }
	            System.err.println("Building VBO");

	            //VBO vbo = VBOFactory.build(currentTextureID, triangleList);

	            //System.err.println("Adding VBO with text id " + currentTextureID + ", with " + triangleList.size() + " triangles to scene.");
	            //scene.addVBO(vbo);
	        }
	        System.err.println("Finally ready to draw things.");

	        
	        
	        
	        //
	    	GLProfile profile = GLProfile.get(GLProfile.GL2);
	    	GLCapabilities capabilities = new GLCapabilities(profile);
	 
	    	GLCanvas glcanvas = new GLCanvas(capabilities);
	    	glcanvas.addGLEventListener(renderer);
	    	glcanvas.setSize( 800, 800 );
	 
	        JFrame frame = new JFrame( "OpenGL Basics" );
	        frame.getContentPane().add( glcanvas);

	        renderer.setModel(model);
	        model.setView(glcanvas);
	        
	        // shutdown the program on windows close event
	        frame.addWindowListener(new WindowAdapter() {
	            public void windowClosing(WindowEvent ev) {
	                System.exit(0);
	            }
	        });
	        
	        frame.setSize( frame.getContentPane().getPreferredSize() );
	        frame.setVisible( true );
	        
	        // set key events
	        frame.addKeyListener(new KeyListener() {
	        	public void keyTyped(KeyEvent e) {}	            
				public void keyPressed(KeyEvent e) {
					if(e.getKeyCode() == KeyEvent.VK_L){
	        			model.switchLight();
	        		}
					if(e.getKeyCode() == KeyEvent.VK_R){
	        			model.switchRender();
	        		}
					if(e.getKeyCode() == KeyEvent.VK_SPACE){
	        			model.switchFreeze();
	        		}
				}
				public void keyReleased(KeyEvent e) {}
	        });
	 	    
	        model.renderLoop();
	    }
	    

	    // iterate over face list from builder, and break it up into a set of face lists by material, i.e. each for each face list, all faces in that specific list use the same material
	    private static ArrayList<ArrayList<Face>> createFaceListsByMaterial(Build builder) {
	        ArrayList<ArrayList<Face>> facesByTextureList = new ArrayList<ArrayList<Face>>();
	        Material currentMaterial = null;
	        ArrayList<Face> currentFaceList = new ArrayList<Face>();
	        for (Face face : builder.faces) {
	            if (face.material != currentMaterial) {
	                if (!currentFaceList.isEmpty()) {
	                    System.err.println("Adding list of " + currentFaceList.size() + " triangle faces with material " + currentMaterial + "  to our list of lists of faces.");
	                    facesByTextureList.add(currentFaceList);
	                }
	                System.err.println("Creating new list of faces for material " + face.material);
	                currentMaterial = face.material;
	                currentFaceList = new ArrayList<Face>();
	            }
	            currentFaceList.add(face);
	        }
	        if (!currentFaceList.isEmpty()) {
	            System.err.println("Adding list of " + currentFaceList.size() + " triangle faces with material " + currentMaterial + "  to our list of lists of faces.");
	            facesByTextureList.add(currentFaceList);
	        }
	        return facesByTextureList;
	    }

	    // @TODO: This is a crappy way to calculate vertex normals if we are missing said normals.  I just wanted 
	    // something that would add normals since my simple VBO creation code expects them.  There are better ways
	    // to generate normals,  especially given that the .obj file allows specification of "smoothing groups".
	    private static void calcMissingVertexNormals(ArrayList<Face> triangleList) {
	        for (Face face : triangleList) {
	            face.calculateTriangleNormal();
	            for (int loopv = 0; loopv < face.vertices.size(); loopv++) {
	                FaceVertex fv = face.vertices.get(loopv);
	                if (face.vertices.get(0).n == null) {
	                    FaceVertex newFv = new FaceVertex();
	                    newFv.v = fv.v;
	                    newFv.t = fv.t;
	                    newFv.n = face.faceNormal;
	                    face.vertices.set(loopv, newFv);
	                }
	            }
	        }
	    }

	    // load and bind the texture we will be using as a default texture for any missing textures, unspecified textures, and/or 
	    // any materials that are not textures, since we are pretty much ignoring/not using those non-texture materials.
	    //
	    // In general in this simple test code we are only using textures, not 'colors' or (so far) any of the other multitude of things that
	    // can be specified via 'materials'. 
	    private static int setUpDefaultTexture(TextureLoader textureLoader, String defaultTextureMaterial) {
	        int defaultTextureID = -1;
	        try {
	            //defaultTextureID = textureLoader.load(defaultTextureMaterial, true);
	        } catch (IOException ex) {
	            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
	            System.err.println("ERROR: Got an exception trying to load default texture material = " + defaultTextureMaterial + " , ex=" + ex);
	            ex.printStackTrace();
	        }
	        System.err.println("INFO:  default texture ID = " + defaultTextureID);
	        return defaultTextureID;
	    }

	    // Get the specified Material, bind it as a texture, and return the OpenGL ID.  Returns he default texture ID if we can't
	    // load the new texture, or if the material is a non texture and hence we ignore it.  
	    private static int getMaterialID(Material material, int defaultTextureID, Build builder, TextureLoader textureLoader) {
	        int currentTextureID;
	        if (material == null) {
	            currentTextureID = defaultTextureID;
	        } else if (material.mapKdFilename == null) {
	            currentTextureID = defaultTextureID;
	        } else {
	            try {
	                File objFile = new File(builder.objFilename);
	                File mapKdFile = new File(objFile.getParent(), material.mapKdFilename);
	                System.err.println("Trying to load  " + mapKdFile.getAbsolutePath());
	                //currentTextureID = textureLoader.load(mapKdFile.getAbsolutePath(), true);
	            } catch (IOException ex) {
	                Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
	                System.err.println("ERROR: Got an exception trying to load  texture material = " + material.mapKdFilename + " , ex=" + ex);
	                ex.printStackTrace();
	                //System.err.println("ERROR: Using default texture ID = " + defaultTextureID);
	                //currentTextureID = defaultTextureID;
	            }
	        }
	        return currentTextureID;
	    }

	    // VBOFactory can only handle triangles, not faces with more than 3 vertices.  There are much better ways to 'triangulate' polygons, that
	    // can be used on polygons with more than 4 sides, but for this simple test code justsplit quads into two triangles 
	    // and drop all polygons with more than 4 vertices. 
	    
	    private static ArrayList<Face> splitQuads(ArrayList<Face> faceList) {
	        ArrayList<Face> triangleList = new ArrayList<Face>();
	        int countTriangles = 0;
	        int countQuads = 0;
	        int countNGons = 0;
	        for (Face face : faceList) {
	            if (face.vertices.size() == 3) {
	                countTriangles++;
	                triangleList.add(face);
	            } else if (face.vertices.size() == 4) {
	                countQuads++;
	                FaceVertex v1 = face.vertices.get(0);
	                FaceVertex v2 = face.vertices.get(1);
	                FaceVertex v3 = face.vertices.get(2);
	                FaceVertex v4 = face.vertices.get(3);
	                Face f1 = new Face();
	                f1.map = face.map;
	                f1.material = face.material;
	                f1.add(v1);
	                f1.add(v2);
	                f1.add(v3);
	                triangleList.add(f1);
	                Face f2 = new Face();
	                f2.map = face.map;
	                f2.material = face.material;
	                f2.add(v1);
	                f2.add(v3);
	                f2.add(v4);
	                triangleList.add(f2);
	            } else {
	                countNGons++;
	            }
	        }
	        int texturedCount = 0;
	        int normalCount = 0;
	        for (Face face : triangleList) {
	            if ((face.vertices.get(0).n != null)
	                    && (face.vertices.get(1).n != null)
	                    && (face.vertices.get(2).n != null)) {
	                normalCount++;
	            }
	            if ((face.vertices.get(0).t != null)
	                    && (face.vertices.get(1).t != null)
	                    && (face.vertices.get(2).t != null)) {
	                texturedCount++;
	            }
	        }
	        System.err.println("Building VBO, originally " + faceList.size() + " faces, of which originally " + countTriangles + " triangles, " + countQuads + " quads,  and  " + countNGons + " n-polygons with more than 4 vertices that were dropped.");
	        System.err.println("Triangle list has " + triangleList.size() + " rendered triangles of which " + normalCount + " have normals for all vertices and " + texturedCount + " have texture coords for all vertices.");
	        return triangleList;
	    }

	}

